package com.xr.common.redis;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.xr.common.utils.PageUtil;
import com.xr.common.utils.XrObjectConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class RedisCacheService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public void set(String key, Object value, Long ttl, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), ttl, timeUnit);
    }

    public void setWithLogicalExpiration(String key, Object value, Long ttl, TimeUnit timeUnit) {
        // 重新封装出包含逻辑过期时间的对象
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpiration(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(ttl)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 根据id查询单个对象方法，解决缓存穿透
     *
     * @param keyPrefix  Redis键前缀
     * @param id         主键id
     * @param clazz      实体类对象（如User.class）
     * @param dbFallback 函数式编程表达式 this::getById（根据id查询单个对象方法）
     * @param time       过期时间的数值
     * @param timeUnit   过期时间的单位
     * @param <K>        实体类泛型（如User）
     * @param <ID>       主键id的数据类型（未知，引入泛型表达）
     * @return <K>       单个实体对象
     */
    public <K, ID> K queryByIdToDealWithCachePenetration(String keyPrefix, ID id, Class<K> clazz, Function<ID, K> dbFallback, Long time, TimeUnit timeUnit) {
        String key = keyPrefix + id;
        String cacheData = this.get(key);
        // 不为空且不为空白字符""
        if (StrUtil.isNotBlank(cacheData)) {
            return JSONUtil.toBean(cacheData, clazz);
        }
        // 不为空（即缓存数据为空白字符""）
        if (null != cacheData) {
            return null;
        }
        K k = dbFallback.apply(id);
        if (ObjectUtil.isEmpty(k)) {
            this.set(key, "", time, timeUnit);
            return null;
        }
        this.set(key, JSONUtil.toJsonStr(k), time, timeUnit);
        return k;
    }

    /**
     * 根据lqw条件查询对象列表方法，lqw条件为null时查询Redis缓存，lqw条件不为null时查询数据库（保证数据的高准确性）
     *
     * @param keyPrefix      Redis键前缀
     * @param clazz          实体类对象（如User.class）
     * @param lqw            查询条件（必须为LambdaQueryWrapper<K>类型且非null）
     * @param dbFallback0    函数式编程表达式1 this::count（查询记录数方法，条件为null）
     * @param dbFallback1    函数式编程表达式2 this::count（查询记录数方法，条件为lqw且非null）
     * @param dbFallback2    函数式编程表达式3 this::list（根据lqw条件查询对象列表方法，条件为lqw）
     * @param idListFallback 函数式编程表达式4 this::getIdList（根据实体对象列表获取id集合）
     * @param <K>            实体类泛型（如User）
     * @param <ID>           id数据类型
     * @return List<K>       实体对象列表
     */
    public <K, ID> List<K> queryListFromRedisIfExists(String keyPrefix, Class<K> clazz, LambdaQueryWrapper<K> lqw, Function<LambdaQueryWrapper<K>, Long> dbFallback0, Function<LambdaQueryWrapper<K>, Long> dbFallback1, Function<LambdaQueryWrapper<K>, List<K>> dbFallback2, Function<List<K>, List<ID>> idListFallback) {
        List<K> kList0 = dbFallback2.apply(null);
        Map<Object, Object> cacheMap;
        K kCache;
        Map<String, Object> map = new HashMap<>();
        // 查询总记录数（lqw == null）
        long length0 = dbFallback0.apply(null);
        // 查询模糊匹配总记录数（lqw != null）
        long length1 = dbFallback1.apply(lqw);
        // 判断当前查询条件是否为null，为null查Redis缓存，不为null查数据库
        List<ID> idList = idListFallback.apply(kList0);
        if (length1 > 0 && length1 == length0) {
            for (int i = (int) length1 - 1; i >= 0; i--) {
                // 先查询Redis中是否有缓存数据，命中，直接返回
                if (ObjectUtil.isNotEmpty(stringRedisTemplate.opsForHash().values(keyPrefix + idList.get(i)))) {
                    cacheMap = stringRedisTemplate.opsForHash().entries(keyPrefix + idList.get(i));
                    kCache = (K) XrObjectConverter.mapToJavaBean(cacheMap, clazz);
                    map.put(keyPrefix + idList.get(i), kCache);
                }
            }
            if (ObjectUtil.isNotEmpty(map) && map.size() > 0) {
                return new ArrayList<>((Collection<? extends K>) map.values());
            }
        }
        // 未命中，查询数据库返回，并存入Redis
        int j = 0;
        List<K> kList1 = dbFallback2.apply(lqw);
        for (K k : kList1) {
            Map<String, Object> kMap = BeanUtil.beanToMap(
                    k, new HashMap<>(),
                    CopyOptions.create()
                            // 自定义字段类型转换规则，解决 例如主键id的Long类型转换为Redis的hash结构存储所需要的String类型 等诸多类型转换问题
                            .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())
            );
            // 判断当前查询条件是否为null，为null时才写入Redis缓存
            if (length1 > 0 && length1 == length0) {
                stringRedisTemplate.opsForHash().putAll(keyPrefix + idList.get(j), kMap);
            }
            j++;
        }
        return kList1;
    }

    /**
     * 一致性新增（保证数据库与Redis缓存中数据的一致性）
     *
     * @param keyPrefix      Redis键前缀
     * @param k              实体类对象（如user）
     * @param dbFallback0    函数式编程表达式1 this::save
     * @param dbFallback1    函数式编程表达式2 this::list
     * @param idListFallback 函数式编程表达式3 this::getIdList
     * @param <K>            实体类泛型（如User）
     * @param <ID>           id数据类型
     * @return boolean       新增成功或失败
     */
    @Transactional
    public <K, ID> boolean insertWithConsistency(String keyPrefix, K k, Function<LambdaQueryWrapper<K>, List<K>> dbFallback0, Function<K, Boolean> dbFallback1, Function<List<K>, List<ID>> idListFallback) {
        List<K> kList = dbFallback0.apply(null);
        boolean flag = dbFallback1.apply(k);
        List<ID> idList = idListFallback.apply(kList);
        for (ID _id : idList) {
            try {
                stringRedisTemplate.delete(keyPrefix + _id.toString());
            } catch (Exception e) {
                log.error("Redis缓存删除失败");
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 一致性更新（保证数据库与Redis缓存中数据的一致性）
     *
     * @param keyPrefix      Redis键前缀
     * @param k              实体类对象（如user）
     * @param dbFallback0    函数式编程表达式1 this::updateById
     * @param dbFallback1    函数式编程表达式2 this::list
     * @param idListFallback 函数式编程表达式3 this::getIdList
     * @param <K>            实体类泛型（如User）
     * @param <ID>           id数据类型
     * @return boolean       更新成功或失败
     */
    @Transactional
    public <K, ID> boolean updateWithConsistency(String keyPrefix, K k, Function<LambdaQueryWrapper<K>, List<K>> dbFallback0, Function<K, Boolean> dbFallback1, Function<List<K>, List<ID>> idListFallback) {
        List<K> kList = dbFallback0.apply(null);
        boolean flag = dbFallback1.apply(k);
        List<ID> idList = idListFallback.apply(kList);
        for (ID _id : idList) {
            try {
                stringRedisTemplate.delete(keyPrefix + _id.toString());
            } catch (Exception e) {
                log.error("Redis缓存删除失败");
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 一致性删除（保证数据库与Redis缓存中数据的一致性）
     *
     * @param keyPrefix      Redis键前缀
     * @param id             主键id
     * @param dbFallback0    函数式编程表达式1 this::removeById
     * @param dbFallback1    函数式编程表达式2 this::list
     * @param idListFallback 函数式编程表达式3 this::getIdList
     * @param <K>            实体类泛型（如User）
     * @param <ID>           id数据类型
     * @return boolean       删除成功或失败
     */
    @Transactional
    public <K, ID> boolean deleteWithConsistency(String keyPrefix, ID id, Function<LambdaQueryWrapper<K>, List<K>> dbFallback0, Function<ID, Boolean> dbFallback1, Function<List<K>, List<ID>> idListFallback) {
        List<K> kList = dbFallback0.apply(null);
        boolean flag = dbFallback1.apply(id);
        List<ID> idList = idListFallback.apply(kList);
        for (ID _id : idList) {
            try {
                stringRedisTemplate.delete(keyPrefix + _id.toString());
            } catch (Exception e) {
                log.error("Redis缓存删除失败");
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 基于Redis缓存改进的查询全部方法
     *
     * @param key        Redis键
     * @param ttl        缓存过期时间
     * @param timeUnit   过期时间单位
     * @param clazz      对象类（如：User.class）
     * @param lqw        MyBatisPlus条件构造器 LambdaQueryWrapper
     * @param dbFallback 函数式编程表达式（如：this::list）
     * @param <K>        实体类泛型（如：User）
     * @return List<K>   实体对象集合（如：userList）
     */
    public <K> List<K> queryAllFromRedisIfExists(String key, Long ttl, TimeUnit timeUnit, Class<K> clazz, LambdaQueryWrapper<K> lqw, Function<LambdaQueryWrapper<K>, List<K>> dbFallback) {
        List<K> kList;
        // 根据条件查询时，查数据库直接返回（数据准确性要求较高）
        if (lqw != null) {
            kList = dbFallback.apply(lqw);
            return kList;
        }
        // 优先查询Redis缓存中的数据
        String kListCache = stringRedisTemplate.opsForValue().get(key);
        // 判断是否为空
        if (StrUtil.isNotBlank(kListCache)) {
            // 不为空，将json字符串解析为对象集合并返回
            kList = JSON.parseArray(kListCache, clazz);
            return kList;
        }
        // 为空，查询数据库并存入Redis缓存然后返回
        kList = dbFallback.apply(null);
        String jsonString = JSON.toJSONString(kList);
        stringRedisTemplate.opsForValue().set(key, jsonString, ttl, timeUnit);
        return kList;
    }

    /**
     * 基于Redis缓存改进的分页查询方法
     *
     * @param keyPrefix   Redis键前缀
     * @param ttl         缓存过期时间
     * @param timeUnit    过期时间单位
     * @param clazz       对象类（如：User.class）
     * @param currentPage 当前页数
     * @param pageSize    每页记录数
     * @param lqw         MyBatisPlus条件构造器 LambdaQueryWrapper
     * @param dbFallback  函数式编程表达式（如：this::list）
     * @param <K>         实体类泛型（如：User）
     * @return PageUtil   分页对象
     */
    public <K> PageUtil queryByPageFromRedisIfExists(String keyPrefix, Long ttl, TimeUnit timeUnit, Class<K> clazz, int currentPage, int pageSize, LambdaQueryWrapper<K> lqw, Function<LambdaQueryWrapper<K>, List<K>> dbFallback) {
        List<K> kList;
        // 根据条件查询时，查数据库直接返回（数据准确性要求较高）
        if (lqw != null) {
            Page<?> page = PageHelper.startPage(currentPage, pageSize);
            kList = dbFallback.apply(lqw);
            int total = (int) page.getTotal();
            return PageUtil.respByPage(kList, currentPage, pageSize, total);
        }
        // 优先查询Redis缓存中的数据
        String key = keyPrefix + ":" + "currentPage:" + currentPage + "pageSize:" + pageSize;
        String pageJsonString = stringRedisTemplate.opsForValue().get(key);
        // 判断是否为空
        if (StrUtil.isNotBlank(pageJsonString)) {
            // 不为空，将json字符串解析为对象集合并返回
            return JSONUtil.toBean(pageJsonString, PageUtil.class);
        }
        // 为空，查询数据库并存入Redis缓存然后返回
        Page<?> page = PageHelper.startPage(currentPage, pageSize);
        kList = dbFallback.apply(null);
        int total = (int) page.getTotal();
        PageUtil pageObject = PageUtil.respByPage(kList, currentPage, pageSize, total);
        String jsonString = JSON.toJSONString(pageObject);
        stringRedisTemplate.opsForValue().set(key, jsonString, ttl, timeUnit);
        return pageObject;
    }

}
